/*============================================================================
 * Copyright 2009 VMware Inc.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at: http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 * License for the specific language governing permissions and limitations 
 * under the License.
 ============================================================================*/

package com.vmware.vsphere.adapter;

import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import net.chrisrichardson.ec2deployer.core.AWSProperties;
import net.chrisrichardson.ec2deployer.core.AbstractCloudManager;
import net.chrisrichardson.ec2deployer.core.CloudCommandExecutor;
import net.chrisrichardson.ec2deployer.core.EC2Server;
import net.chrisrichardson.ec2deployer.core.EC2ServerHolder;
import net.chrisrichardson.ec2deployer.core.ProcessMonitor;
import net.chrisrichardson.ec2deployer.core.Ssh;

import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.TaskInfoState;
import com.vmware.vim25.VirtualMachineCloneSpec;
import com.vmware.vim25.VirtualMachinePowerState;
import com.vmware.vim25.VirtualMachineRelocateSpec;
import com.vmware.vim25.mo.ComputeResource;
import com.vmware.vim25.mo.Folder;
import com.vmware.vim25.mo.HostSystem;
import com.vmware.vim25.mo.ManagedEntity;
import com.vmware.vim25.mo.SearchIndex;
import com.vmware.vim25.mo.ServiceInstance;
import com.vmware.vim25.mo.Task;
import com.vmware.vim25.mo.VirtualMachine;
import com.vmware.vim25.mo.util.MorUtil;

/**
 * @author Steve Jin (sjin@vmware.com)
 */

public class VMwareCloudManager extends AbstractCloudManager 
{
	private EC2ServerHolder ec2Servers = new EC2ServerHolder();
	
	AWSProperties awsProps;
	private String startupScript;
	private Ssh ssh;
	private String ip;
	private Vector<String> stopVMs = new Vector<String>();
	
    public VMwareCloudManager(AWSProperties awsp) 
    {
        this.awsProps = awsp;
        this.ssh = new Ssh(awsp);
        if (awsProps.exists("startupScript")) 
        {
            startupScript = (String) awsp.getProperty("startupScript");
        }
    }
	public boolean associateAddress(EC2Server arg0, String arg1) 
	{
		//System.out.println("=======>associateAddress");
		return false;
	}

	public EC2Server findInstance(String id) 
	{
		//System.out.println("=======>findInstance():" + id);
		// in our case, we use inventory path as the instance ID of VM
		String vmTemplate = (String)awsProps.getProperty("vSphere.vmTemplatePath");
		EC2Server ec2s = new EC2Server(this, id, vmTemplate, "running", "NA");
		
		try
		{
			ServiceInstance si = connect2vSphere();
			SearchIndex search = si.getSearchIndex();
			VirtualMachine vm = (VirtualMachine) search.findByInventoryPath(id);
			String ip = getIPAddr(vm);
			ec2s.getInstanceState().updateStatus("running", ip, ip);
			si.getServerConnection().logout();
		} catch(Exception e)
		{
			e.printStackTrace();
		}
		return ec2s;
	}

	public AWSProperties getAwsProperties() 
	{
		return this.awsProps;
	}

	public List<EC2Server> getServers() 
	{
		//System.out.println("=======>getServers()");
		return this.ec2Servers.getServers();
	}

	public CloudCommandExecutor getSsh() 
	{
		//System.out.println("=======>getSsh()");
		return this.ssh;
	}

	public String getStartupScript() 
	{
		return this.startupScript;
	}

	public List<EC2Server> newServers(int n, String instanceType,
			Map<String, String> extraParams) 
	{
		//System.out.println("=======>newServers(): n=" + n + ";instanceType:" + instanceType + ";extraParams" + extraParams);

		// get current time for unique VM names
		long time = new Date().getTime();

		List<EC2Server> ec2List = new ArrayList<EC2Server>();
		String vmTemplate = (String)awsProps.getProperty("vSphere.vmTemplatePath");
		String vmDir = vmTemplate.substring(0,vmTemplate.lastIndexOf("/")+1);

		for(int i=0; i<n; i++)
		{
			String vmName = "J2EE_" + time +"_" + i;
			
			ip = createNewVM(vmName); 
			
			EC2Server ec2s = new EC2Server(this, vmDir+vmName, vmTemplate, "running", "NA");
			ec2s.getInstanceState().updateStatus("running", ip, ip);
			ec2List.add(ec2s);
		}

		this.ec2Servers.addAll(ec2List);
		return ec2List;
	}

	public List<EC2Server> newServersElsewhere(int arg0, String arg1,
			EC2Server arg2, Map<String, String> arg3) 
	{
		System.out.println("newServersElsewhere(3) is called");
		return null;
	}

	public void pollStatus() 
	{
		//System.out.println("=======>pollStatus().ec2Servers.length:" + ec2Servers.getServers().size());
		try
		{
			ServiceInstance si = connect2vSphere();
			SearchIndex searchIndex = si.getSearchIndex();
			
			for(EC2Server ec2s : ec2Servers.getServers())
			{
				//System.out.println("=======>ec2s:" + ec2s.getInstanceId());
				VirtualMachine vm = (VirtualMachine) searchIndex.findByInventoryPath(ec2s.getInstanceId());
//				* Does not exist - status -> gone
//				* Exists and is powered on -> running
//				* Exists and is powered off -> terminated
				if(vm==null)
				{
					ec2s.updateStatus("gone", getIPAddr(vm), ec2s.getInstanceId());
				}
				else if(vm.getRuntime().getPowerState()==VirtualMachinePowerState.poweredOn)
				{
					String ip = getIPAddr(vm);
					ec2s.updateStatus("running", ip, ip);
				}
				else
				{
					ec2s.updateStatus("terminated", "0.0.0.0", "0.0.0.0");
				}
			}
			si.getServerConnection().logout();
		} catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	public List<?> reserve(int n, String arg1) 
	{
		//System.out.println("=======>reserve();n=" + n + "arg1=" + arg1);
		return null;
	}

	public void setDefaultProcessMonitor(ProcessMonitor arg0) 
	{
		//System.out.println("=======>setDefaultProcessMonitor()");
	}

	public void setServers(List<EC2Server> servers) 
	{
		//System.out.println("=======>setServers()");
		this.ec2Servers.addAll(servers);
	}

	public void startPolling(Object interval) 
	{
		//System.out.println("=======>startPolling()");
		Timer timer = new Timer();
		timer.schedule(new PollTask(), 0, 15 * 1000);
	}
		
	class PollTask extends TimerTask 
	{
	    public void run() 
	    {
	    	pollStatus();
	    }
    }

	public void stop(EC2Server ec2s) 
	{
		//System.out.println("=======>stop():arg0=" + ec2s);
		if(stopVMs.contains(ec2s.getInstanceId()))
		{
			return;
		}
		else
		{
			stopVMs.add(ec2s.getInstanceId());
		}
		try
		{
			ServiceInstance si = connect2vSphere();
			SearchIndex searchIndex = si.getSearchIndex();
			
			VirtualMachine vm = (VirtualMachine) searchIndex.findByInventoryPath(ec2s.getInstanceId());
			Task task = vm.powerOffVM_Task();
			TaskInfoState tis;
			for(;;)
			{
				Thread.sleep(3*1000);
				tis = task.getTaskInfo().getState();
				if(tis == TaskInfoState.success || tis == TaskInfoState.error)
				{
					break;
				}
			}
			
			if(tis == TaskInfoState.success)
			{
				vm.destroy_Task();
			}
			si.getServerConnection().logout();
		} catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	private ServiceInstance connect2vSphere() throws RemoteException, MalformedURLException
	{
		return new ServiceInstance(
				new URL((String)awsProps.getProperty("vSphere.apiURL")), 
				(String)awsProps.getProperty("vSphere.user"), 
				(String)awsProps.getProperty("vSphere.password"), true);
	}
	
	private String createNewVM(String vmName)
	{
		try
		{
			ServiceInstance si = connect2vSphere();
			SearchIndex searchIndex = si.getSearchIndex();
			
			VirtualMachine vmTemplate = (VirtualMachine) searchIndex.findByInventoryPath(
					(String)awsProps.getProperty("vSphere.vmTemplatePath"));
			System.out.println("vmTempStr=" + (String)awsProps.getProperty("vSphere.vmTemplatePath"));
			System.out.println("vmTemp=" + vmTemplate);
			Folder vmFolder = (Folder) vmTemplate.getParent();
			
			ManagedEntity[] mes = vmFolder.getChildEntity();
			
			for(int i=0; mes!=null && i < mes.length; i++)
			{
				if(mes[i].getName().indexOf("J2EE_precloned")!=-1)
				{
					mes[i].rename_Task(vmName);
					if(((VirtualMachine)mes[i]).getRuntime().getPowerState()==VirtualMachinePowerState.poweredOff)
					{
						((VirtualMachine)mes[i]).powerOnVM_Task(null);
					}
					String ip = getIPAddr((VirtualMachine)mes[i]);
					String preCloneVmName = "J2EE_precloned" + new Date().getTime();
					cloneVM(vmTemplate, vmFolder, preCloneVmName);
					return ip; //we are not tracking the clone task here.
				}
			}
			
			Task task = cloneVM(vmTemplate, vmFolder, vmName);
			TaskInfoState tis;
			for(;;)
			{
				Thread.sleep(3*1000);
				tis = task.getTaskInfo().getState();
				if(tis == TaskInfoState.success || tis == TaskInfoState.error)
				{
					break;
				}
			}
			
			if(tis == TaskInfoState.success)
			{
				System.out.println("VM Deployment Succeeded!");
				ManagedObjectReference newVmMOR = (ManagedObjectReference) task.getTaskInfo().getResult();
				VirtualMachine newVM = (VirtualMachine) MorUtil.createExactManagedEntity(si.getServerConnection(), newVmMOR);
				return getIPAddr(newVM);
			}
			else if(tis == TaskInfoState.error)
			{
				System.out.println("Deploy task failed.");
			}
			si.getServerConnection().logout();
		} catch(Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}
	
	private String getIPAddr(VirtualMachine vm) throws InterruptedException
	{
		for(;;)
		{
			Thread.sleep(3*1000);
			String ip = vm.getGuest().getIpAddress();
			if(ip!=null)
			{
				System.out.println("ip addr:" + ip);
				return ip;
			}
		}
	}
	
	private Task cloneVM(VirtualMachine vmTemplate, Folder vmFolder, String vmName)	throws RemoteException 
	{
		ManagedObjectReference hostMOR = vmTemplate.getRuntime().getHost();
		HostSystem host = (HostSystem) MorUtil.createExactManagedEntity(vmTemplate.getServerConnection(), hostMOR);
		ManagedObjectReference rourcePoolMOR = (ManagedObjectReference)((ComputeResource)host.getParent()).getPropertyByPath("resourcePool");
		
		VirtualMachineCloneSpec vmcs = new VirtualMachineCloneSpec();
		vmcs.powerOn = true;
		vmcs.template = false;
		VirtualMachineRelocateSpec vmrs = new VirtualMachineRelocateSpec();
		vmrs.setPool(rourcePoolMOR);
		vmcs.setLocation(vmrs);

		Task task = vmTemplate.cloneVM_Task(vmFolder, vmName, vmcs);
		return task;
	}
}